/*
 * SocketSSL.cpp
 *
 *  Created on: 12.12.2011
 *      Author: Pavel Sinay
 */

#include "SocketSSL.h"
#include "constants.h"
#include "Lock.h"
#include <iostream>
#include <openssl/err.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

static const char KEYFILE[] = "sinay2.pri";
static const char CERTI[] =  "sinay2.csr";

SocketSSL::SocketSSL() {
	SSL_library_init();
	SSL_load_error_strings();
	m_bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
}

SocketSSL::~SocketSSL() {
	disconnect();
}

int SocketSSL::connectTCP(std::string ip, size_t port) {
	m_fdsocket = socket(AF_INET, SOCK_STREAM, 0);
	if (m_fdsocket < 0) {
		std::cerr << "Socket error: socket" << std::endl;
		m_fdsocket = 0;
		return C_ERROR;
	}

	sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(port);

	if (inet_aton(ip.c_str(), &serv_addr.sin_addr) < 0) {
		std::cerr << "Socket error: inet_aton" << std::endl;
		disconnect();
		return C_ERROR;
	}

	if (connect(m_fdsocket, (sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) {
		std::cerr << "Socket error: connect" << std::endl;
		disconnect();
		return C_ERROR;
	}

	return C_OK;
}

int SocketSSL::connectServer(std::string ip, size_t port) {
	if (connectTCP(ip, port) == C_ERROR) {
		return C_ERROR;
	}

	m_ctx = initializeCTX();
	if (m_ctx == NULL) {
		return C_ERROR;
	}

	m_ssl = SSL_new(m_ctx);
	m_sbio = BIO_new_socket(m_fdsocket, BIO_NOCLOSE);

	SSL_set_bio(m_ssl, m_sbio, m_sbio);

	if (SSL_connect(m_ssl) <= 0) {
		printSSLError("SSL connect error");
		disconnect();
		return C_ERROR;
	}

	return C_OK;
}

int SocketSSL::disconnect() {
	SSL_CTX_free(m_ctx);
	return SocketTCP::disconnect();
}

int SocketSSL::sendMessage(std::vector<char> const& msg) {
	Lock lock(&m_mutex);
	lock;

	int result = SSL_write(m_ssl, (void*) &msg[0], msg.size());
	if (SSL_get_error(m_ssl, result) == SSL_ERROR_NONE) {
		return C_OK;
	} else {
		return C_ERROR;
	}
}

int SocketSSL::receiveMessage(std::vector<char> & msg) {
	Lock lock(&m_mutex);
	lock;

	msg.clear();
	size_t total_rc = 0;
	msg.resize(CONST_BUFFER_SIZE);

	uint32_t block_size;
	receiveBlock((char*) &block_size, sizeof(block_size));
	*(int*) &msg[0] = block_size;
	total_rc = sizeof(block_size);
	int received = 0;
	do {
		received = receiveBlock(&msg[total_rc], CONST_BUFFER_SIZE);
		total_rc += received;
	} while (total_rc != block_size + sizeof(block_size));

	msg.resize(total_rc);
	return total_rc == 0 ? C_ERROR : C_OK;
}

int SocketSSL::receiveBlock(char* msg, size_t size) {
	int result = SSL_read(m_ssl, (void*) &msg[0], size);
	if (SSL_get_error(m_ssl, result) == SSL_ERROR_NONE) {
		return result;
	} else {
		return C_ERROR;
	}
}

void SocketSSL::printSSLError(std::string string) {
	BIO_printf(m_bio_err, "%s\n", string.c_str());
	ERR_print_errors(m_bio_err);
}

SSL_CTX *SocketSSL::initializeCTX() {
	SSL_METHOD *meth;
	SSL_CTX *ctx;

	meth = SSLv23_method();
	ctx = SSL_CTX_new(meth);

	if (!(SSL_CTX_use_certificate_chain_file(ctx, CERTI))) {
		printSSLError("Can't read certificate file");
		SSL_CTX_free(ctx);
		return NULL;
	}

	if (!(SSL_CTX_use_PrivateKey_file(ctx, KEYFILE, SSL_FILETYPE_PEM))) {
		printSSLError("Can't read key file");
		SSL_CTX_free(ctx);
		return NULL;
	}

	return ctx;
}
